﻿/// <reference path="../../cannon/cannon.d.ts" />
/// <reference path="../../lib/egret3d.d.ts" />

class Picker
{
    _canvasEgret: egret3d.Egret3DCanvas;
    _view3D: egret3d.View3D;

    N: number = 20;
    gplane: egret3d.Mesh;
    clickMarker: egret3d.Mesh;

    start()
    {
        this.initCannon();

        this._canvasEgret = new egret3d.Egret3DCanvas();
        this._canvasEgret.x = 0;
        this._canvasEgret.y = 0;
        this._canvasEgret.width = window.innerWidth;
        this._canvasEgret.height = window.innerHeight;
        this._canvasEgret.start();

        var lightGroup = new egret3d.LightGroup();
        var light = new egret3d.DirectLight(new egret3d.Vector3D(0, -1, 1));
        light.diffuse = 0xffffff;
        light.intensity = 1;
        lightGroup.addLight(light);

        this._view3D = new egret3d.View3D(0, 0, window.innerWidth, window.innerHeight);
        this._view3D.camera3D.lookAt(new egret3d.Vector3D(0, 3, -15), new egret3d.Vector3D(0, 2, 0));
        this._view3D.backColor = 0xff333333;
        this._canvasEgret.addView3D(this._view3D);

        var plane = new egret3d.Mesh(new egret3d.PlaneGeometry(500, 500), new egret3d.ColorMaterial());
        plane.material.diffuseColor = 0xaaaaaa;
        plane.material.acceptShadow = true;
        plane.material.castShadow = true;
        plane.material.lightGroup = lightGroup;
        this._view3D.addChild3D(plane);

        var boxGeometry = new egret3d.CubeGeometry(1, 1, 1);
        for (var i = 0; i < this.N; i++)
        {
            var boxMesh = new egret3d.Mesh(boxGeometry, new egret3d.ColorMaterial());
            boxMesh.material.castShadow = true;
            boxMesh.material.lightGroup = lightGroup;
            this._view3D.addChild3D(boxMesh);
            this.meshes.push(boxMesh);
            boxMesh.pickType = egret3d.PickType.UVPick;
            boxMesh.enablePick = true;
            boxMesh.addEventListener(egret3d.PickEvent3D.PICK_DOWN, this.onMouseDown, this);
            boxMesh.addEventListener(egret3d.PickEvent3D.PICK_UP, this.onMouseUp, this);
        }

        this._canvasEgret.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        egret3d.Input.addEventListener(egret3d.Event3D.RESIZE, this.onResize, this);

    }

    setClickMarker(x: number, y: number, z: number)
    {
        if (!this.clickMarker)
        {
            this.clickMarker = new egret3d.Mesh(new egret3d.SphereGeometry(0.1, 16, 8), new egret3d.ColorMaterial());
            this.clickMarker.material.diffuseColor = 0xaa0000;
            this._view3D.addChild3D(this.clickMarker);
        }
        this.clickMarker.visible = true;
        this.clickMarker.position = new egret3d.Vector3D(x, y, z);
    }

    removeClickMarker()
    {
        this.clickMarker.visible = false;
    }

    onMouseMove(e: egret3d.PickEvent3D)
    {
        if (this.gplane && this.mouseConstraint)
        {
            var pos = e.pickResult.globalPosition;
            this.setClickMarker(pos.x, pos.y, pos.z);
            this.moveJointToPoint(pos.x, pos.y, pos.z);
        }
    }

    onMouseDown(e: egret3d.PickEvent3D)
    {
        var pos = e.pickResult.globalPosition;
        if (pos)
        {
            this.constraintDown = true;
            
            this.setClickMarker(pos.x, pos.y, pos.z);
            
            this.setScreenPerpCenter(pos);

            var mesh = e.target as egret3d.Mesh;
            var index = this.meshes.indexOf(mesh);
            if (index != -1)
            {
                this.addMouseConstraint(pos.x, pos.y, pos.z, this.bodies[index]);
            }
        }
    }

    constraintDown: boolean = false;
    onMouseUp(e: egret3d.PickEvent3D)
    {
        this.constraintDown = false;
        // remove the marker
        this.removeClickMarker();

        // Send the remove mouse joint to server
        this.removeJointConstraint();

        this._view3D.removeChild3D(this.gplane);
    }

    // This function creates a virtual movement plane for the mouseJoint to move in
    setScreenPerpCenter(point: egret3d.Vector3D)
    {
        var planeGeo = new egret3d.PlaneGeometry(100, 100);
        this.gplane = new egret3d.Mesh(planeGeo, new egret3d.ColorMaterial());
        //this.gplane.visible = false;
        this.gplane.material.alpha = 0;
        this._view3D.addChild3D(this.gplane);
        this.gplane.pickType = egret3d.PickType.UVPick;
        this.gplane.enablePick = true;
        this.gplane.addEventListener(egret3d.PickEvent3D.PICK_MOVE, this.onMouseMove, this);
        this.gplane.addEventListener(egret3d.PickEvent3D.PICK_UP, this.onMouseUp, this);

        // Center at mouse position
        this.gplane.position = point;

        // Make it face toward the camera
        //this.gplane.orientation = this._view3D.camera3D.orientation;
        this.gplane.rotationX = 90;
    }

    world: CANNON.World;
    boxShape: CANNON.Shape;
    boxBody: CANNON.Body;
    jointBody: CANNON.Body;
    bodies: CANNON.Body[] = [];
    meshes: egret3d.Mesh[] = [];
    initCannon()
    {
        this.world = new CANNON.World();
        this.world.quatNormalizeSkip = 0;
        this.world.quatNormalizeFast = false;

        this.world.gravity.set(0, -10, 0);
        this.world.broadphase = new CANNON.NaiveBroadphase();

        // create boxes
        var mass = 5
        var radius = 1.3;
        this.boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
        for (var i = 0; i < this.N; i++)
        {
            this.boxBody = new CANNON.Body({ mass: mass });
            this.boxBody.addShape(this.boxShape);
            this.boxBody.position.set(Math.random() + 0.5, 5 + i * 4, Math.random() + 0.5);
            this.world.addBody(this.boxBody);
            this.bodies.push(this.boxBody);
        }

        // create joint body
        var shape = new CANNON.Sphere(0.1);
        this.jointBody = new CANNON.Body({ mass: 0 });
        this.jointBody.addShape(shape);
        this.jointBody.collisionFilterGroup = 0;
        this.jointBody.collisionFilterMask = 0;
        this.world.addBody(this.jointBody)

        // create plane
        var groundShape = new CANNON.Plane();
        var groundBody = new CANNON.Body({ mass: 0 });
        groundBody.addShape(groundShape);
        groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
        this.world.addBody(groundBody);
    }

    constrainedBody: CANNON.Body;
    pivot: CANNON.Vec3;
    mouseConstraint: CANNON.PointToPointConstraint;
    addMouseConstraint(x: number, y: number, z: number, body: CANNON.Body)
    {
        // The cannon body constrained by the mouse joint
        this.constrainedBody = body;

        // Vector to the clicked point, relative to the body
        var v1 = new CANNON.Vec3(x, y, z).vsub(this.constrainedBody.position);

        // Apply anti-quaternion to vector to tranform it into the local body coordinate system
        var antiRot = this.constrainedBody.quaternion.inverse();
        this.pivot = antiRot.vmult(v1); // pivot is not in local body coordinates

        // Move the cannon click marker particle to the click position
        this.jointBody.position.set(x, y, z);

        // Create a new constraint
        // The pivot for the jointBody is zero
        this.mouseConstraint = new CANNON.PointToPointConstraint(this.constrainedBody, this.pivot, this.jointBody, new CANNON.Vec3(0, 0, 0));

        // Add the constriant to world
        this.world.addConstraint(this.mouseConstraint);
    }

    // This functions moves the transparent joint body to a new postion in space
    moveJointToPoint(x: number, y: number, z: number)
    {
        // Move the joint body to a new position
        this.jointBody.position.set(x, y, z);
        this.mouseConstraint.update();
    }

    removeJointConstraint()
    {
        // Remove constriant from world
        this.world.removeConstraint(this.mouseConstraint);
        this.mouseConstraint = null;
    }

    update()
    {
        this.updatePhysics();
    }

    fps: number = 60;
    time: number = Date.now();
    updatePhysics()
    {
        this.fps = 1000 / (Date.now() - this.time);
        this.time = Date.now();

        this.world.step(1 / this.fps);

        for (var i = 0; i < this.meshes.length; i++)
        {
            this.meshes[i].position = new egret3d.Vector3D(this.bodies[i].position.x, this.bodies[i].position.y, this.bodies[i].position.z);
            this.meshes[i].orientation = new egret3d.Quaternion(this.bodies[i].quaternion.x, this.bodies[i].quaternion.y, this.bodies[i].quaternion.z, this.bodies[i].quaternion.w);
        }
    }

    onResize(e: egret3d.Event3D)
    {
        this._view3D.width = this._canvasEgret.width;
        this._view3D.height = this._canvasEgret.height;
    }
}

window.onload = () =>
{
    var a = new Picker();
    a.start();
};
